﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;

namespace cimdict
{
    public class CIMString
    {
        private string text = "";
        private int pos = 0;

        public static string format(string desc)
        {
            desc = desc.Trim();
            if (desc.Length > 0) if (desc[0] == '"') desc = desc.Substring(1);
            if (desc.Length > 0) if (desc[desc.Length - 1] == '"') desc = desc.Substring(0, desc.Length - 1);
            desc = desc.Replace("\"\"", "");
            desc = desc.Replace("\"\n\"", "\n");
            desc = desc.Replace("\\'", "'");
            desc = desc.Replace("\\\"", "\"");
            desc = desc.Replace("\\n", "\n");
            return desc;
        }

        public CIMString(string line)
        {
            this.pos = 0;
            this.text = line;
        }

        public CIMString(string[] lines)
        {
            this.pos = 0;
            this.text = "";
            foreach (string line in lines)
            {
                //cut off all space
                string ln = line.Trim();
                //skip comment
                if (ln.IndexOf("//") == 0) continue;
                //reformat text
                if (this.text == "")
                {
                    this.text = ln;
                }
                else
                {
                    this.text = this.text +"\n"+ ln;
                }
            }
        }

        //Find the first occur of CHR in STR
        private static int find( string STR, char CHR, int startPos )
        {
            return findmatch(STR, '\0', CHR, startPos);
        }

        //Find the matched endSEP of startSEP
        private static int findmatch(string STR, char startSEP, char endSEP, int startPos)
        {
            bool inQuot = false;
            bool inTran = false;
            int deepth = 0;
            for (int i = startPos; i < STR.Length; i++)
            {
                if (inTran)
                {
                    inTran = false;
                    continue;
                }

                if (STR[i] == '"')
                {
                    inQuot = !inQuot;
                }
                if (STR[i] == '\\')
                {
                    inTran = true;
                }

                if (inQuot)
                {
                    continue;
                }

                if (STR[i] == startSEP) deepth++;
                if (STR[i] == endSEP)
                {
                    if (deepth == 0)
                    {
                        return i;
                    }
                    else
                    {
                        deepth--;
                    }
                }
            }
            return -1;
        }

        public string readBlock(char startSEP, char endSEP)
        {
            int startCnt = find(this.text, startSEP, this.pos);
            if (startCnt < 0) return "";
            startCnt++;

            int endCnt = findmatch(this.text, startSEP, endSEP, startCnt);
            if (endCnt < 0) return "";

            string ret = "";
            if (endCnt > 0)
            {
                ret = this.text.Substring(startCnt, endCnt - startCnt);
                this.pos = endCnt + 1;
            }
            return ret;
        }

        //Example: Description ( "definition of SMX_AutoStart")
        public string readLabeledBlock(string leadingSign, char startSEP, char endSEP)
        {
            int startCnt = this.pos;
            while (startCnt >= 0)
            {
                startCnt = this.text.IndexOf(leadingSign, startCnt);
                if (startCnt < 0) break;
                startCnt += leadingSign.Length;
                string subtext = this.text.Substring(startCnt).Trim();
                if (subtext.IndexOf(startSEP) == 0) break;
            }
            if (startCnt < 0) return "";

            this.pos = startCnt;
            return this.readBlock(startSEP, endSEP);
        }

        //Example: class SMX_AutoStart {....}
        public string readUnkownBlock(char startSEP, char endSEP, out string name)
        {
            name = "";
            int startCnt = find(this.text, startSEP, this.pos);
            if (startCnt < 0) return "";
            name = this.text.Substring(this.pos, startCnt - this.pos);
            this.pos = startCnt;
            return this.readBlock(startSEP, endSEP);
        }

        public string readTill(char endSEP)
        {
            int startCnt = this.pos;
            int endCnt = find(this.text, endSEP, startCnt);
            if (endCnt < 0) return "";
            string subtext = this.text.Substring(startCnt, endCnt - startCnt);
            this.pos = endCnt + 1;

            return subtext;
        }

    }

    public class CIMProp
    {
        public string desc = "";
        public string fullName = "";
        public string name = "";
        public string nameLower = "";
        public string param = "";
        public bool isFunc = true;

        protected void setName(string nm)
        {
            this.name = nm;
            this.nameLower = nm.ToLower();
        }

        public bool equalTo(string pn)
        {
            return this.nameLower.Equals(pn);
        }
        public bool equalTo(CIMProp prop)
        {
            return this.nameLower.Equals(prop.nameLower);
        }

        public CIMProp(string propDef, string propText)
        {
            this.param = (new CIMString(propDef)).readUnkownBlock('(', ')', out this.fullName);
            if (this.fullName == "")
            {
                this.isFunc = false;
                this.fullName = propDef;
            }

            string[] parts;
            if (this.fullName.IndexOf('=') >= 0)
            {
                string[] parts1 = this.fullName.Split('=');
                parts = parts1[0].Trim().Split(' ');
            }
            else
            {
                parts = this.fullName.Split(' ');
            }
            int nameIdx = parts.Length - 1;
            string propName = "";
            while (propName.Length <= 1 && nameIdx >= 0 )
            {
                propName = parts[nameIdx].Trim();
                nameIdx--;
            }
            propName = propName.Replace("[", "");
            propName = propName.Replace("]", "");
            propName = propName.Trim();
            this.setName(propName);

            this.desc = CIMString.format( (new CIMString(propText)).readLabeledBlock("Description", '(', ')') );
            string vlm = (new CIMString(propText)).readLabeledBlock("ValueMap", '{', '}');
            string vls = (new CIMString(propText)).readLabeledBlock("Values", '{', '}');

            if ((vls != "") && (vlm != ""))
            {
                vls = vls.Replace("\"", "");
                vlm = vlm.Replace("\"", "");
                string[] vlsa = vls.Split(',');
                string[] vlma = vlm.Split(',');
                if (vlsa.Length == vlma.Length)
                {
                    this.desc += "\n\n-------Value Maps--------";
                    int i = 0;
                    foreach (string nm in vlsa)
                    {
                        this.desc = this.desc + "\n" + vlsa[i] + " = " + vlma[i];
                        i++;
                    }
                }
            }
        }
    }

    public class CIMClass
    {
        public string name = "";
        public string nameLower = "";
        public string baseName = "";
        public string baseNameLower = "";

        public string desc = "";
        public string ver = "";

        public Collection<CIMProp> props = new Collection<CIMProp>();
        public CIMClass baseClass = null;

        public string fileName = "";

        protected void setName(string nm)
        {
            this.name = nm;
            this.nameLower = nm.ToLower();
        }

        protected void setBaseName(string nm)
        {
            this.baseName = nm;
            this.baseNameLower = nm.ToLower();
        }

        public bool inheritFrom(string baseName)
        {
            return this.baseNameLower.Equals(baseName);
        }
        public bool inheritFrom(CIMClass baseCim)
        {
            return this.baseNameLower.Equals(baseCim.nameLower);
        }
        public bool equalTo(string compName)
        {
            return this.nameLower.Equals(compName);
        }
        public bool equalTo(CIMClass cim)
        {
            return this.nameLower.Equals(cim.nameLower);
        }
        public bool contains(string key)
        {
            if (this.nameLower.IndexOf(key) >= 0)
            {
                return true;
            }
            return false;
        }

        public CIMClass(string fn)
        {
            this.fileName = fn;
            string classdef = "";

            CIMString all = new CIMString(File.ReadAllLines(fn));
            CIMString header = new CIMString(all.readBlock('[', ']'));
            CIMString body = new CIMString(all.readUnkownBlock('{', '}', out classdef));

            //Version ( "1.0.0" )
            this.ver = CIMString.format( header.readLabeledBlock("Version", '(', ')') );
            //Description ( "definition of SMX_AutoStartFCHBA")
            this.desc = CIMString.format( header.readLabeledBlock("Description", '(', ')') );

            //class SMX_AutoStartFCHBA  : SMX_AutoStart
            Match classDefMatch = Regex.Match(classdef, @"^\s*?class\s*?(.*?)\s*?:\s*?(.*?)$");
            if (classDefMatch.Success)
            {
                this.setName( classDefMatch.Result("$1").ToString().Trim() );
                this.setBaseName(classDefMatch.Result("$2").ToString().Trim());
            }
            else
            {
                // class CIM_DAPort {
                classDefMatch = Regex.Match(classdef, @"^\s*?class\s*?(.*?)$");
                if (classDefMatch.Success)
                {
                    this.setName(classDefMatch.Result("$1").ToString().Trim());
                    this.setBaseName("");
                }
            }

            while (true)
            {
                string bodytext = body.readBlock('[', ']');
                string propdef = body.readTill(';');
                if (propdef == "") break;

                this.props.Add(new CIMProp(propdef, bodytext));
            }
        }

        public CIMProp getProp(string name)
        {
            foreach (CIMProp prop in this.props)
            {
                if (prop.equalTo(name))
                {
                    return prop;
                }
            }
            return null;
        }

    }

    public class CIMRepo
    {
        protected CIMRepo() 
        { 
        }

        protected static List<string> MOFFiles = new List<string>();
        protected static List<CIMClass> CIMClasses = new List<CIMClass>();

        public static List<CIMClass> getAllClasses()
        {
            return CIMRepo.CIMClasses;
        }

        protected static List<string> getAllMOF(string path)
        {
            if (path == "")
            {
                string appName = Process.GetCurrentProcess().MainModule.FileName;
                path = Path.GetDirectoryName(appName);
            }
            List<string> result = Directory.GetFiles(path, "*.mof").ToList();
            List<string> dirs = Directory.GetDirectories(path).ToList();
            foreach (string dir in dirs)
            {
                foreach (string mofile in getAllMOF(dir))
                {
                    result.Add(mofile);
                }
            }

            return result;
        }

        protected static bool addClass(CIMClass cim)
        {
            if (cim.name == null || cim.name == "")
            {
                return false;
            }
            foreach (CIMClass existCim in CIMRepo.CIMClasses)
            {
                if (cim.equalTo(existCim))
                {
                    return false;
                }
            }
            CIMRepo.CIMClasses.Add(cim);
            return true;
        }

        public static void init()
        {
            CIMRepo.MOFFiles = CIMRepo.getAllMOF("");

            //Read all MOF files
            //int i = 0;
            foreach (string filename in CIMRepo.MOFFiles)
            {
                //Console.WriteLine("# " + i++);
                CIMClass cim = new CIMClass(filename);
                CIMRepo.addClass( cim );
            }
            CIMRepo.CIMClasses.Sort(delegate(CIMClass c1, CIMClass c2) { return c1.name.CompareTo(c2.name); });

            //Link base class
            foreach (CIMClass cim in CIMRepo.CIMClasses)
            {
                if (cim.baseName != "")
                {
                    foreach (CIMClass cimBase in CIMRepo.CIMClasses)
                    {
                        if (cimBase.equalTo(cim.baseName))
                        {
                            cim.baseClass = cimBase;
                            break;
                        }
                    }
                }
            }

            //end init
        }


        public static CIMClass getClass(string name)
        {
            foreach (CIMClass cim in CIMRepo.CIMClasses)
            {
                if (cim.equalTo(name))
                {
                    return cim;
                }
            }
            return null;
        }
    
    }
}
